#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>
#include <sys/stat.h>
#include <string.h>
#include <syslog.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <security/pam_modutil.h>
#include <security/pam_ext.h>

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "otp.h"

#define PAM_EXTERN	extern

#define CONFIG_FILE_NAME ".pam_mytotp.conf"

// ------------
static char *str_skip_prefix(const char *str, const char *prefix) {
	return strncmp(str, prefix, strlen(prefix)) ? NULL : (char *)(str + strlen(prefix));
}
static int is_number(const char *s) {
	char *p;
	p = (char *)s;
	while(*p != '\0') {
		if (*p >= '0' && *p <= '9') {
			
		} else {
			return 0;
		}
		p++;
	}
	return 1;
}
// ------------
static int check_passwd(pam_handle_t *pamh, const char *password, struct passwd *user) {
	
	if (password == NULL || user == NULL) {
		pam_syslog(pamh, LOG_ERR, "检测不通过，参数为NULL");
		return 1;
	}
	
	char *userhome;
	char *username;
	username = user->pw_name;
	userhome = user->pw_dir;
	
	if (userhome == NULL) {
		pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，获取用户目录失败", username);
		return 1;
	}
	
	// 获取配置文件路径
	char path[128] = "";
	
	if (sizeof(path) < strlen(userhome) + strlen(CONFIG_FILE_NAME) + 2) {
		pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp配置文件路径超长", username);
		return 1;
	}

	strcat(path, userhome);
	strcat(path, "/");
	strcat(path, CONFIG_FILE_NAME);
	
	pam_syslog(pamh, LOG_INFO, "%s 检测，，，otp配置文件为 %s", username, path);
	
	// 配置文件权限检查
	struct stat statbuf;
	if (stat(path, &statbuf)) {
		pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp配置文件状态信息获取不到", username);
		return 1;
	}
	
	if (!S_ISREG(statbuf.st_mode)) {
		pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp配置文件不是普通文件", username);
		return 1;
	}
	
	if (statbuf.st_size <= 0 || statbuf.st_size > 1024 * 10) {
		pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp配置文件为空或长度超过10KB", username);
		return 1;
	}
	
	if (user->pw_uid != statbuf.st_uid) {
		pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp配置文件所有者非当前用户", username);
		return 1;
	}
	
	if ((statbuf.st_mode & 0x01ff) != 0x0180) { // 低9位需要是600，即110 000 000
		pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp配置文件权限不是0600: 0%o", username, statbuf.st_mode & 0x01ff);
		return 1;
	}
	
	// 配置文件读取
	FILE *fp = fopen(path,"r");
	if (fp == NULL) {
		pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp配置文件打开不成功，可能不存在或无权限", username);
		return 1;
	}
	
	// 获取配置信息
	char key[200] = "";
	char key_format[10] = "string"; // base64 hex string
	char window[6] = "3";
	char interval[6] = "30";
	
	char buf[256], *tmp, *match;
	int err = 0;
	while(fgets(buf, sizeof(buf), fp) != NULL) {
		// 认为给够配置长度空间了，超长直接退出
		if (strlen(buf) > sizeof(buf) - 1) {
			pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp配置文件限定每行最多255个字符", username);
			err = 1;
			break;
		}
		
		// 忽略注释
		if (buf[0] == '#') {
			continue;
		}
		
		// 去除空白
		tmp = memchr(buf, '\r', sizeof(buf));
		if (tmp != NULL) {
			*tmp = '\0';
		}
		tmp = memchr(buf, '\n', sizeof(buf));
		if (tmp != NULL) {
			*tmp = '\0';
		}
		// 匹配参数
		if ((match = str_skip_prefix(buf, "key=")) != NULL) {
			if (strlen(match) >= sizeof(key) + 1) {
				pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp配置项key超长", username);
				err = 1;
				break;
			}
			key[0] = '\0';
			strcat(key, match);
		} else if ((match = str_skip_prefix(buf, "key_format=")) != NULL) {
			if (strlen(match) >= sizeof(key_format) + 1) {
				pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp配置项key_format超长", username);
				err = 1;
				break;
			}
			key_format[0] = '\0';
			strcat(key_format, match);
		} else if ((match = str_skip_prefix(buf, "window=")) != NULL) {
			if (strlen(match) >= sizeof(window) + 1) {
				pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp配置项window超长", username);
				err = 1;
				break;
			}
			window[0] = '\0';
			strcat(window, match);
		} else if ((match = str_skip_prefix(buf, "interval=")) != NULL) {
			if (strlen(match) >= sizeof(interval) + 1) {
				pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp配置项interval超长", username);
				err = 1;
				break;
			}
			interval[0] = '\0';
			strcat(interval, match);
		} else {
			
		}
	}
	fclose(fp);
	
	memset(buf, 0, sizeof(buf));
	if (err) {
		memset(key, 0, sizeof(key));
		return 1;
	}
	
	pam_syslog(pamh, LOG_INFO, "%s 检测，，，otp参数：key=%s window=%s interval=%s", username,
		(key == NULL || key[0] == '\0') ? "" : "***",
		window, interval);
	
	// 进行验证
	if (key[0] == '\0') {
		pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp参数项key为空", username);
		memset(key, 0, sizeof(key));
		return 1;
	}
	if (key_format[0] == '\0') {
		strcat(key_format, "string");
	}
	if (window[0] == '\0') {
		strcat(window, "3");
	}
	if (interval[0] == '\0') {
		strcat(interval, "30");
	}
	if (!is_number(window) || !is_number(interval)) {
		pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp参数window或interval不是数字", username);
		memset(key, 0, sizeof(key));
		return 1;
	}
	
	char secret[64];
	int secret_length;
	if (strlen(key) > 32 || otp_convert_secret(key, key_format, secret, &secret_length) != OTP_SUCC) {
		pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，otp参数校验不成功", username);
		memset(key, 0, sizeof(key));
		memset(secret, 0, sizeof(secret));
		return 1;
	}
	
	totp_ctx ctx;
	totp_init(&ctx, NULL, atoi(window), atoi(interval));
	totp_set_secret(&ctx, secret, secret_length);
	
	int retval = totp_verify(&ctx, password);
	
	memset(key, 0, sizeof(key));
	memset(secret, 0, sizeof(secret));
	
	if (retval == OTP_SUCC) {
		pam_syslog(pamh, LOG_INFO, "%s 检测通过", username);
		return 0;
	}
	pam_syslog(pamh, LOG_INFO, "%s 检测不通过，口令错误", username);
	return 1;
}

static int pam_converse(pam_handle_t *pamh, const char *item_msg, int item_type) {
	int retval;
	char *item_val;
	
	struct pam_message message, *pmessage;
	struct pam_response *response;
	struct pam_conv *conv;
	
	pmessage = &message;
	message.msg_style = PAM_PROMPT_ECHO_OFF;
	message.msg = item_msg;
	response = NULL;
	
	// 获取会话函数
	retval = pam_get_item(pamh, PAM_CONV, (const void **)(void *) &conv);
	if (retval != PAM_SUCCESS) {
		return retval;
	}
	
	// 调用会话
	retval = conv->conv(1, (const struct pam_message **) &pmessage, (struct pam_response **) &response, conv->appdata_ptr);
	if (retval != PAM_SUCCESS) {
		return retval;
	}
	
	if (response) {
		item_val = (*response).resp;
		(*response).resp = NULL;
	} else {
		return PAM_CONV_ERR;
	}
	
	free(response);	
	pam_set_item(pamh, item_type, item_val);
	return PAM_SUCCESS;
}

// ------------
PAM_EXTERN
int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
			const char **argv)
{
	int retval;

	const char *username;
	char *password;
	struct passwd *user;
	
	char *superpasswd;

	// 获取用户
	if ((retval = pam_get_user(pamh, &username, NULL)) != PAM_SUCCESS) {
		pam_syslog(pamh, LOG_WARNING, "检测不通过，获取用户失败");
		return retval;
	}

	if ((user = pam_modutil_getpwnam(pamh, username)) == NULL) {
		pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，获取用户信息失败", username);
		return PAM_USER_UNKNOWN;
	}

	// 获取密码
	pam_get_item(pamh, PAM_AUTHTOK, (void*) &password);
	if (password == NULL) {
		retval = pam_converse(pamh, "Password: ", PAM_AUTHTOK);
		if (retval != PAM_SUCCESS) {
			pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，获取用户口令失败", username);
			return retval;
		}
		pam_get_item(pamh, PAM_AUTHTOK, (void*) &password);
	}
	
	if (password == NULL && (flags & PAM_DISALLOW_NULL_AUTHTOK)) {
		pam_syslog(pamh, LOG_WARNING, "%s 检测不通过，需要提供密码", username);
		return PAM_AUTH_ERR;
	}
	
	// 格式检查
	if (strlen(password) < 6) {
		pam_syslog(pamh, LOG_WARNING, "%s 检测忽略，密码过短", username);
		return PAM_AUTH_ERR;
	}
/*
	// 超级密码
	int i;
	for (i = 0; i < argc; i++) {
		superpasswd = str_skip_prefix(argv[i], "superpasswd=");
	}
	
	// 超级密码验证
	if (superpasswd != NULL && strlen(superpasswd) > 0 && strcmp(password,superpasswd) == 0) {
		pam_syslog(pamh, LOG_WARNING, "%s 检测通过，使用超级密码", username);
		return PAM_SUCCESS;
	}
*/
	// totp密码验证
	if (!is_number(password)) {
		return PAM_AUTH_ERR;
	}

	if (check_passwd(pamh, password, user) == 0) {
		return PAM_SUCCESS;
	}

	return PAM_AUTH_ERR;
}

PAM_EXTERN
int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
		   const char **argv)
{
	return PAM_SUCCESS;
}
